﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Boo.Lang;
using NLog;
using NGinnBPM.Lib.Interfaces;
using NGinnBPM.Lib.Schema;
using NGinnBPM.Lib.Data;
using NGinnBPM.Lib.Exceptions;
using NGinnBPM.MessageBus;
using NGinnBPM.Runtime;
using NGinnBPM.Services;
using NGinnBPM.Services.Utilities;

namespace NGinnBPM.Services.Scripting.BooScript
{

    public abstract class ScriptBase : ITaskScript
    {
        public delegate object ExpressionDelegate();
        private Dictionary<string, ExpressionDelegate> _expressions = new Dictionary<string, ExpressionDelegate>();
        private Dictionary<string, Action> _actions = new Dictionary<string, Action>();
        private IQuackFu _data;
        private TaskInstance _inst;
        private ITaskContext _context;
        protected Logger log = LogManager.GetCurrentClassLogger();
        private string _taskId;
        private IQuackFu _cfg;

        public ScriptBase()
        {
            _cfg = new CfgWrapper(this);
        }
        /// <summary>
        /// Source data 
        /// </summary>
        public IQuackFu data
        {
            get { return _data; }
            set { _data = value; }
        }

        /// <summary>
        /// Configuration
        /// </summary>
        public IQuackFu cfg
        {
            get { return _cfg; }
            set { _cfg = value; }
        }

        private class CfgWrapper : IQuackFu
        {
            private ScriptBase _sb;
            internal CfgWrapper(ScriptBase sb)
            {
                _sb = sb;
            }

            private IConfigParameterProvider GetCfg()
            {
                return _sb.context.ServiceLocator.GetInstance<IConfigParameterProvider>();
            }

            #region IQuackFu Members

            public object QuackGet(string name, object[] parameters)
            {
                return GetCfg().GetString(name);
            }

            public object QuackInvoke(string name, params object[] args)
            {
                throw new NotImplementedException();
            }

            public object QuackSet(string name, object[] parameters, object value)
            {
                throw new NotImplementedException();
            }

            #endregion
        }

        /// <summary>
        /// Current task instance. Null if there's no task instance yet (for example, when binding input data)
        /// </summary>
        [DuckTyped]
        public TaskInstance taskInstance
        {
            get { return _inst; }
            set { _inst = value; }
        }

        /// <summary>
        /// Task definition ID. Should be set on initialization.
        /// </summary>
        public string TaskId
        {
            get { return _taskId; }
            set { _taskId = value; }
        }

        [DuckTyped]
        public Task taskDefinition
        {
            get { return context.ParentProcess.GetTask(TaskId); }
        }

        public ProcessDefinition process
        {
            get { return context.ParentProcess; }
        }
        
        /// <summary>
        /// Task context. Should be set on initialization.
        /// </summary>
        public ITaskContext context
        {
            get { return _context; }
            set { _context = value; }
        }

        public abstract void Initialize();

        protected void add_expression(string alias, ExpressionDelegate expr)
        {
            _expressions[alias] = expr;
        }

        protected void add_action(string alias, Action act)
        {
            _actions[alias] = act;
        }


        public ExpressionDelegate GetExpression(string name)
        {
            ExpressionDelegate ed;
            return _expressions.TryGetValue(name, out ed) ? ed : null;
        }

        public Action GetCodeBlock(string name)
        {
            Action act;
            return _actions.TryGetValue(name, out act) ? act : null;
        }

        
        #region ITaskScript Members

        private Dictionary<string, object> _srcData;
        public Dictionary<string, object> SourceData
        {
            get
            {
                return _srcData; 
            }
            set {
                _srcData = value;
                data = new QuackDynWrapper(new DicWrap( value)); 
            }
        }

        public ITaskContext TaskContext
        {
            get {return _context;}
            set{_context = value;}
        }
        
        public TaskInstance TaskInstance
        {
            get { return _inst; }
            set { _inst = value; }
        }

        public object GetInputParameterValue(string paramName)
        {
            ScriptBase.ExpressionDelegate ed = GetExpression(BooProcessScriptManager.GetTaskInputParameterBindingAlias(TaskId, paramName));
            return ed();
        }

        public object GetDefaultVariableValue(string variableName)
        {
            ScriptBase.ExpressionDelegate ed = GetExpression(BooProcessScriptManager.GetDefaultVariableValueAlias(TaskId, variableName));
            return ed();
        }

        public object RunScriptBlock(string blockId)
        {
            Action act = GetCodeBlock(BooProcessScriptManager.GetTaskCodeBlockAlias(TaskId, blockId));
            if (act == null) throw new Exception("Code block undefined: " + blockId);
            act();
            return null;
        }

        public object EvalInputVariableBinding(string varName)
        {
            ScriptBase.ExpressionDelegate ed = GetExpression(BooProcessScriptManager.GetInputVariableBindingAlias(TaskId, varName));
            return ed();
        }

        public object EvalOutputVariableBinding(string varName)
        {
            ScriptBase.ExpressionDelegate ed = GetExpression(BooProcessScriptManager.GetOutputVariableBindingAlias(TaskId, varName));
            return ed();
        }

        public object EvalMultiInstanceSplitQuery()
        {
            ScriptBase.ExpressionDelegate ed = GetExpression(BooProcessScriptManager.GetMultiInstanceSplitQueryAlias(TaskId));
            return ed();
        }

        public bool EvaluateFlowInputCondition(Flow fl)
        {
            ScriptBase.ExpressionDelegate ed = GetExpression(BooProcessScriptManager.GetFlowAlias(fl));
            return Convert.ToBoolean(ed());
        }

        /// <summary>
        /// round-robing function for selecting list item in a cyclic manner
        /// </summary>
        /// <param name="counterId">Unique id of the counter</param>
        /// <param name="items">array of items to choose from</param>
        /// <returns></returns>
        protected object round_robin(string counterId, object[] items)
        {
            IPersistentCounters pc = context.ServiceLocator.GetInstance<IPersistentCounters>();
            if (pc == null) throw new Exception("IPersistentCounters missing");
            string cnid = string.Format("{0}_{1}_{2}", this.process.DefinitionId, this.TaskId, counterId);
            int n = pc.Increment(cnid);
            return items[n % items.Length];
        }

        private static Random _rouletteRand = new Random();

        /// <summary>
        /// Roulette wheel function for selecting random item based on selection probability (weighs parameter)
        /// </summary>
        /// <param name="items"></param>
        /// <param name="weighs"></param>
        /// <returns></returns>
        protected object roulette(object[] items, double[] weighs)
        {
            if (items.Length == 0) throw new Exception("roulette needs at least one  item");
            if (weighs.Length == 0) throw new Exception("roulette needs at least one nonzero weight");
            double sum = 0;
            double[] incr = new double[items.Length];
            for (int i = 0; i < incr.Length; i++)
            {
                if (i < weighs.Length) sum += weighs[i];
                incr[i] = sum;
            }
            double result = _rouletteRand.NextDouble() * sum;
            for (int i = 0; i < incr.Length; i++)
                if (result <= incr[i]) return items[i];
            return items[items.Length - 1];
        }

        protected object iif(bool cond, object val, object elseVal)
        {
            return cond ? val : elseVal;
        }

        
        #endregion

        #region ITaskScript Members


        public object EvalOutputParameterBinding(string varName)
        {
            ScriptBase.ExpressionDelegate ed = GetExpression(BooProcessScriptManager.GetTaskOutputParameterBindingAlias(TaskId, varName));
            return ed();
        }

        #endregion

        public string ProcessInstanceId
        {
            get { return taskInstance.ProcessInstanceId; }
        }



       
    }
}
